Generated code - Linq to LLBLGen Pro, general usage
Preface
This section contains the usage descriptions of some of the Linq constructs and extension methods available. Not all constructs are discussed here: prefetch paths usage and function mappings have their own sections, as well as the section about the limitations of Linq to LLBLGen Pro in this release and which Linq constructs and extension methods we didn't implement (and the reasons why). Furthermore, extension methods / Linq constructs which are implemented as described in the MSDN documentation aren't discussed here, as that would be redundant. If a method isn't discussed on this page or elsewhere in this manual, you can assume the behavior of the method is as described in the MSDN documentation of that method.
This section will also describe the extension methods we added to enrich the Linq support to expose elements of the LLBLGen Pro API which weren't covered by the Linq syntax.
Important: |
Linq queries against a database, using the LinqMetaData queryable object can still contain in-memory constructs, like creating Lists, arrays, calling a method etc. Linq to LLBLGen Pro tries as much as possible to execute this code locally in-memory, if appropriate: either when the data is fetched and placed into objects, or when the query is constructed.
It can be that this isn't always possible, for example if you specify a method call on an in-memory object which relies on data known only during the query execution in the database. Typically this occurs when Linq to Object queries are merged with Linq to LLBLGen Pro queries. It's recommended that you don't merge these queries, unless you're sure it will work (and not throw an exception at runtime): keep database queries separated from in-memory (Linq to Objects) queries as much as possible: not only is it more clear what's executed where, but it's also less error prone. |
Aggregates
Linq supports five aggregate functions: Count(
predicate), Sum, Max, Min and Average. There's a 6th, LongCount, but it can be seen as the same aggregate as Count, only that it will return a long/Int64 instead of an int/Int32.
LLBLGen Pro supports more aggregate functions than Linq currently offers. The following IQueryable extension methods have been added to support the extra aggregates LLBLGen Pro supports:
([] marks optional parameters (there are different overloads)).
- CountColumn(field [, bool applyDistinct]). This method can count on a field and if true is passed for applyDistinct, the count will be a Count distinct. Currently Count() is a CountRow (which is a Count(*))
- StandardDeviation(field [, bool applyDistinct]). This method is similar to StDev in normal databases. If true is specified for applyDistinct, it will be a distinct StDev. Distinct is only supported on a small set of databases. Please consult your databases' manual if it supports StdDev with distinct.
- Variance(field [, bool applyDistinct]). This method is similar to Var(field) in normal databases. If true is specified for applyDistinct it will be a distinct Var. Distinct is only supported on a small set of databases. Please consult your databases' manual if it supports Var with distinct.
You can call these methods on a query or a set inside a query similar to when you call Max, Sum, Count or the other standard Linq aggregate functions.
Note:
|
VB.NET users shouldn't use the Aggregate keyword, as the VB.NET compiler doesn't generate any Expression tree from the Aggregate usage in some situations due to a bug in the VB.NET compiler. Instead it will produce a query which fetches all data of the target table/query into memory and apply the aggregate function on that data. This is very inefficient. As it's unclear when the VB.NET compiler refuses to produce a proper Expression tree and when it works, it's recommended to use the extension methods to be safe. So instead of:
Dim max = Aggregate o In metaData.Order Into m=Max(o.OrderDate)
do:
Dim max = metaData.Order.Max(Function(o) o.OrderDate)
|
Note: |
VB.NET users should use the .Value property of Nullable(Of T) typed fields instead of just the field in filter expressions, like expressions in the Where clause. The VB.NET compiler introduces coalesce statements on these boolean expressions if it detects a Nullable(Of T) typed element in the expression which are useless and which bloat the query unnecessary. Linq to LLBLGen Pro strips off these needless coalesce expressions if it runs into one of them (except in the projection (select)), however it can be it can't make the proper decision and the query fails.
Example of a proper query (o.EmployeeId is a Nullable(Of Integer) typed field) :
Dim q = From o In metaData.Order _
Where o.EmployeeId.Value=2 _
Select o
Example of a query which contains the Coalesce operator around EmployeeId=2:
Dim q = From o In metaData.Order _
Where o.EmployeeId=2 _
Select o
The unnecessary Coalesce expression is detected in this case, however to avoid unnecessary errors and unnecessary complex SQL, use the '.Value' property on the Nullable(Of T) typed fields.
To filter on Nothing (null), it's necessary to use the field instead of the '.Value' property, though this is ok, as the VB.NET compiler doesn't wrap these expressions with a coalesce statement:
Dim q = From o In metaData.Order _
Where o.EmployeeId = Nothing _
Select o
|
Group by
Grouping of data is an important aspect of Linq and also of Linq to LLBLGen Pro. Together with aggregate functions on the grouped data, complex queries can be written. Linq to LLBLGen Pro supports group by constructs in all possible situations, including grouping on constants, grouping on compound keys (using a new anonymous type, see example below) and specifying group targets in the projection. Below, you'll find two examples of the usage of group by in a Linq to LLBLGen Pro query: one groups on multiple fields, the other one shows how to produce a hierarchical set where per group key (the field(s) the set was grouped on) the matching data elements (e.g. entities) are stored.
Group by multiple keys:
// per country-city the # of customers is determined.
var q = from c in metaData.Customer
group c by new { c.Country, c.City } into g
select new { g.Key, g.Count()};
' per country-city the # of customers is determined.
Dim q = From c In metaData.Customer _
Group By c.Country, c.City _
Into Count() _
Select Country, City, Count
Select group data in hierarchy:
// returns set of objects which contain per country the customers in a hierarchy
var q = from c in metaData.Customer
group c by c.Country into g
select g;
' returns set of objects which contain per country the customers in a hierarchy
Dim q = From c In metaData.Customer _
Group c By c.Country Into G = Group
The second example shows a fetch of a hierarchical set: per key (country) the set of customers in that country is returned. Linq to LLBLGen Pro has an efficient hierarchical fetch engine which produces two queries for this fetch. Other Linq providers like Linq to Sql produce per key a query, which can result in much lower performance. Later on in this section, the hierarchical fetches are more discussed in detail.
Note:
|
VB.NET users shouldn't append the Into grouping clause with a Select statement if they want to refer to the whole Group, like in the example above where the group is stored in variable 'G'. Doing so will wrap the nested query into a derived table, which makes it impossible to fetch it, as a nested query can only be present in the projection. |
Order by
You can sort on any field in the projection or in the scope of the query, as you'd expect in a Linq query. As Linq also allows you to specify functions inside the query, you can also sort on the outcome of a function. See the following example where all Customer entities are sorted on the second character in the CustomerId:
var q = from c in metaData.Customer
orderby c.CustomerId[1]
select c;
Dim q = From c In metaData.Customer _
Order By c.CustomerId(1) _
Select c
Type filtering and casting
LLBLGen Pro supports entity inheritance and this gives the requirement of being able to filter on types in a query and also to be able to cast an entity to a given entity type. To be able to filter on a type, LLBLGen Pro requires a
type filter. With Linq to LLBLGen Pro, it's easy to insert a type filter: by using the Queryable extension methods
Cast or
TypeOf, or by using the C#/VB.NET keywords
as/As and
is/Is.
It's recommended to use Cast<T> or TypeOf<T> over 'as'. The following example shows why:
// Query 1
var q = from e in metaData.Employee
let x = e as BoardMemberEntity
where x.CompanyCarID==3
select e;
// Query 2
var q = from e in metaData.Employee.Cast<BoardMemberEntity>()
where e.CompanyCarID==3
select e;
// Query 3
var q = from e in metaData.Employee.TypeOf<BoardMemberEntity>()
where e.CompanyCarID==3
select e;
' Query 1
Dim q = From e In metaData.Employee _
Let x = e As BoardMemberEntity _
Where x.CompanyCarID=3 _
Select e
' Query 2
Dim q = From e In metaData.Employee.Cast(Of BoardMemberEntity)() _
Where e.CompanyCarID=3 _
Select e
' Query 3
Dim q = From e In metaData.Employee.TypeOf(Of BoardMemberEntity)()
Where e.CompanyCarID=3 _
Select e
Query 1 shows the usage of
let to store a temporary query result in a variable. While this results in the usage of a typefilter in the final query being executed, it does have a design flaw: what if 'e' isn't a BoardMemberEntity? In normal code, it should throw an exception on the next line as 'x' is null / Nothing in that case. In case of Linq to LLBLGen Pro, no exception is thrown, as the let expression is converted into a SQL query fragment, and there's no way to throw the exception as the SQL query has no notion of the inheritance mapping. Query 2 and Query 3 are all doing the same thing as Query 1, but they're conceptually different: they first cast the employee to the right type (BoardMemberEntity) before working with that entity. One could argue that TypeOf is actually better over Cast, as Cast also should throw an exception if 'e' isn't casteable to BoardMemberEntity.
Usage of 'as/As' in filters and projections
The keyword 'as/As' can be used in filters but it's not supported in projections. For using 'as/As' in filters, use the following rules of thumb:
- (variable as type) operand expression results in type operand expression which always results in a typefilter.
- (variable as type).Field operand expression results in a predicate where Field is seen as a field of the entity of type type.
Example queries of usage of 'as/As' in a filter (supported) and in projections (not supported):
// as in a filter, supported. Fetches all BoardMemberEntity instances
var q = from e in metaData.Employee
where (e as BoardMemberEntity) != null
select e;
// as in a projection. Not supported.
var q = from e in metaData.Employee
select new { SomeField = e as BoardMemberEntity};
' As in a filter, supported. Fetches all BoardMemberEntity instances
Dim q = From e In metaData.Employee _
Where Not (e As BoardMemberEntity) Is Nothing _
Select e
' As in a projection. Not supported.
Dim q = From e In metaData.Employee _
Select SomeField = e As BoardMemberEntity
Queryable: Contains
Contains is the Queryable extension method which allows a developer to produce IN queries or more precisely: queries which filter on another set, be it a set of in-memory objects or a set formed by another Linq query. Linq to LLBLGen Pro has deep support for Contains in all possible situations where Contains can be used.
Contains is also a method of the
String class, which is converted to a LIKE filter. The
Contains examples below are focussing on Queryable.Contains. String extension methods which result in LIKE filters, the StartsWith/EndsWith and Contains methods, are discussed below in their own paragraph.
Examples of Contains queries. Linq to LLBLGen Pro also supports Contains calls on in-memory objects with multiple fields. These objects should have fields in common with the primary key of the entity filtered.
// Query 1, simple entity check in entity list
var q = from c in metaData.Customer
where c.Orders.Where(o=>o.EmployeeId==3).Contains(order)
select c;
// Query 2, operand is entity which is result of query
var q = from c in metaData.Customer
where c.Orders.Contains(
(from o in metaData.Order
where o.EmployeeId == 2 select o).First())
select c;
// Query 3, operand and source are both queries.
var q = from c in metaData.Customer
where c.Orders.Where(o => o.EmployeeId == 2).Contains(
(from o in metaData.Order
where o.EmployeeId == 2 select o).First())
select c;
// Query 4, constant compare with value from query. Yes this is different.
var q = from c in metaData.Customer
where c.Orders.Where(o => o.EmployeeId > 3).Select(o => o.ShipVia).Contains(2)
select c;
// Query 5, check if a constant tuple is in the result of a query
var q = from c in metaData.Customer
where c.Orders.Select(oc => new { EID = oc.EmployeeId, CID = oc.CustomerId }).Contains(
new { EID = (int?)1, CID = "CHOPS" })
select c;
// Query 6, as 5 but now compare with a tuple created with a query
var q = from c in metaData.Customer
where c.Orders.Select(oc => new { EID = oc.EmployeeId, CID = oc.CustomerId }).Contains(
(from o in metaData.Order where o.CustomerId == "CHOPS"
select new { EID = o.EmployeeId, CID = o.CustomerId }).First())
select c;
// Query 7, checking if the value of a field in an entity is in a list of constants
List<string> countries = new List<string>() { "USA", "UK" };
var q = from c in metaData.Customer
where countries.Contains(c.Country)
select c;
// Query 8, as 7 but now with an IEnumerable
LinkedList<string> countries = new LinkedList<string>(new string[] { "USA", "UK"});
var q = from c in metaData.Customer
where countries.Contains(c.Country)
select c;
// Query 9, combination of 2 queries where the first is merged with the second and
// only the second is executed. (this is one of the reasons why you have to write
// your own Funcletizer code.
var q1 = (from c in metaData.Customer
select c.Country).Distinct();
var q2 = from c in metaData.Customer
where q1.Contains(c.Country)
select c;
// Query 10, as 7 but now with an array obtained from another array.
string[][] countries = new string[1][] { new string[] { "USA", "UK" } };
var q = from c in metaData.Customer
where countries[0].Contains(c.Country)
select c;
// Query 11, complex contains query with comparison of in-memory object list
List<Pair<string, string>> countryCities = new List<Pair<string, string>>();
countryCities.Add(new Pair<string, string>("USA", "Portland"));
countryCities.Add(new Pair<string, string>("Brazil", "Sao Paulo"));
// now fetch all customers which have a tuple of country/city in the list of countryCities.
var q = from c in metaData.Customer
where countryCities.Contains(
(from c2 in metaData.Customer
where c2.CustomerId == c.CustomerId
select new Pair<string, string>()
{ Value1 = c2.Country, Value2 = c2.City }).First())
select c;
' Query 1, simple entity check In entity list
Dim q = From c In metaData.Customer _
Where c.Orders.Where(Function(o) o.EmployeeId.Value=3).Contains(order) _
Select c
' Query 2, operand is entity which is result of query
Dim q = From c In metaData.Customer _
Where c.Orders.Contains( _
(From o In metaData.Order _
Where o.EmployeeId.Value = 2 Select o).First()) _
Select c
' Query 3, operand and source are both queries.
Dim q = From c In metaData.Customer _
Where c.Orders.Where(Function(o) o.EmployeeId.Value = 2).Contains( _
(From o In metaData.Order _
Where o.EmployeeId = 2 Select o).First()) _
Select c
' Query 4, constant compare with value From query. Yes this is different.
Dim q = From c In metaData.Customer _
Where c.Orders.Where(Function(o) o.EmployeeId.Value > 3).Select(Function(o) o.ShipVia).Contains(2) _
Select c
' Query 5, check if a constant tuple is In the result of a query
Dim q = From c In metaData.Customer _
Where c.Orders.Select(Function(oc) New With {.EID = oc.EmployeeId, .CID = oc.CustomerId}).Contains( _
New With { .EID = CType(1, Nullable(Of integer)), .CID = "CHOPS" }) _
Select c
' Query 6, as 5 but now compare with a tuple created with a query
Dim q = From c In metaData.Customer
Where c.Orders.Select(Function(oc) New With { ,EID = oc.EmployeeId, .CID = oc.CustomerId }).Contains( _
(From o In metaData.Order Where o.CustomerId = "CHOPS" _
Select New With {.EID = o.EmployeeId, .CID = o.CustomerId }).First()) _
Select c
' Query 7, checking if the value of a field In an entity is In a list of constants
Dim countries As New List(Of String)()
countries.Add("USA")
countries.Add("UK")
Dim q = From c In metaData.Customer _
Where countries.Contains(c.Country) _
Select c
' Query 8, as 7 but now with an IEnumerable
Dim countryValues(2) As String
countryValues(0)="USA"
countryValues(1)="UK"
Dim countries As new LinkedList(Of String)(countryValues)
Dim q = From c In metaData.Customer _
Where countries.Contains(c.Country) _
Select c
' Query 9, combination of 2 queries where the first is merged with the second and
' only the second is executed. (this is one of the reasons why you have to write
' your own Funcletizer code.
Dim q1 = (From c In metaData.Customer _
Select c.Country).Distinct()
Dim q2 = From c In metaData.Customer _
Where q1.Contains(c.Country) _
Select c
' Query 10, as 7 but now with an array obtained from another array.
Dim countries(0)() As String
Dim countryValues(1) As String
countryValues(0)="USA"
countryValues(1)="UK"
countries(0)=countryValues
Dim q = From c In metaData.Customer _
Where countries(0).Contains(c.Country) _
Select c
' Query 11, complex Contains query with comparison of In-memory object list
Dim countryCities As New List(Of Pair(Of String, String))()
countryCities.Add(new Pair(Of String, String)("USA", "Portland"))
countryCities.Add(new Pair(Of String, String)("Brazil", "Sao Paulo"))
' now fetch all customers which have a tuple of country/city In the list of countryCities.
Dim q = From c In metaData.Customer _
Where countryCities.Contains( _
(From c2 In metaData.Customer _
Where c2.CustomerId = c.CustomerId _
Select New Pair(Of String, String)() With _
{ .Value1 = c2.Country, .Value2 = c2.City }).First()) _
Select c
String: Contains, String.StartsWith and String.EndsWith
Calling
Contains, StartsWith or
EndsWith on a string-typed field or variable in a Linq query will result in the expression being converted to a LIKE filter. These three methods are different from the rest of the string methods which are mapped to database functions: Contains, StartsWith and EndsWith are converted to a FieldLikePredicate, if required, or if the expression can be evaluated in-memory first, it's evaluated before it's handled further (e.g. calling one of these three methods on a constant string or an in-memory
variable).
The string passed to the methods doesn't have to
be padded with LIKE wildcards, this is done by the Linq to LLBLGen Pro provider, based on the method called.
Paging through resultsets
To page through a resultset returned by a Linq query, one can use two different mechanisms: use
TakePage(pageNumber, pageSize) or use
Skip together with
Take. Linq to LLBLGen Pro's paging mechanism is the same as the paging mechanism build into the querying API of the LLBLGen Pro framework: one specifies a page number and a page size and the fetch logic will retrieve that page of data for the caller. This gives a bit of a conflict with the Skip-Take tandem, which in theory offers the ability to skip x rows and then take y rows where y isn't a multiple of x. Example: skip 3 rows then take 10. Because Linq to LLBLGen Pro is built on top of the LLBLGen Pro querying API, the value passed to Skip and Take have to be usable to calculate the page number and page size from. This means that if you use Skip in a query, you have to specify Take as well
and the value passed to Take has to be a multiple of the value passed to Skip. When in doubt, it's recommended to use the method TakePage on the query as it's a Queryable extension method, added by LLBLGen Pro. Be aware that although you can specify Skip and Take anywhere in the query, as well as TakePage, you should use the methods only to page the
resultset of the query. This means a page action in the middle of a query, e.g. in one side of a join, isn't supported.
Below is an example of how to retrieve the 2nd page of 20 Customer entities.
var q = (from c in metaData.Customer orderby c.CustomerId select c).TakePage(2, 20);
Dim q = (From c In metaData.Customer Order By c.CustomerId Ascending Select c).TakePage(2, 20)
Be aware of the fact that, if the resultset doesn't have a proper ordering specified (through an Order by clause), paging is useless due to the SQL definition that a set inside SQL has no particular ordering defined. Linq to LLBLGen Pro doesn't add an ordering on all fields or on the PK field(s) if there's no ordering specified; you have to specify the ordering yourself. This is required because a paging query is executed again for each page requested and SQL by definition doesn't return a resultset in a given order, the order is undefined, unless an ordering using Order by is specified, which is unique. If you order on a field which gives multiple rows with the same value for the field used to order on (e.g. Customer.Country, with multiple customers from the same country), the order of these rows with the same value is undefined so for each request of a page, the order of these rows could be random, depending on the optimization inside the RDBMS used, because the SQL standard dictates that a set (also the returned set) is in an unknown order (except when an ordering is specified).
ElementAt / ElementAtOrDefault
ElementAt / ElementAtOrDefault is implemented in Linq to LLBLGen Pro as .TakePage(n, 1), where
n is the value passed to ElementAt / ElementAtOrDefault. It is required that the resultset you call this method on (i.e. the query) is ordered with a proper order by clause.
Using a Context
The Linq provider supports using a context instance (
Adapter,
SelfServicing). The LinqMetaData class has a property which accepts a Context,
ContextToUse. Setting this property to a Context instance will make all queries created with the LinqMetaData instance use the set Context. Resetting the property to null will make all queries created with that LinqMetaData instance after that reset action not use the Context previously set. This is illustrated in the example below:
// metaData is a LinqMetaData instance
metaData.ContextToUse = new Context();
// this query will use the context set
var q = from c in metaData.Customer
where ...
select ...
// resetting
metaData.ContextToUse = null;
// this query won't use the context previously set, as the metaData's property
// is null.
var q = from o in metaData.Order
...
' metaData is a LinqMetaData instance
metaData.ContextToUse = New Context()
' this query will use the context set
Dim q = From c In metaData.Customer _
Where ... _
Select ...
' resetting
metaData.ContextToUse = Nothing
' this query won't use the context previously set, as the metaData's property
' is null.
Dim q = From o In metaData.Order _
...
Excluding / Including fields
Linq to LLBLGen Pro supports the exclusion and inclusion of fields (
Adapter,
SelfServicing). This is done through the extension methods
ExcludeFields and
IncludeFields. Using these methods, the developer can specify which fields to exclude in an entity fetch (ExcludeFields) or which fields to use (IncludeFields). The following example illustrates how to exclude two fields from an entity fetch.
// exclude Photo and Notes from employee fetch
var q = (from e in metaData.Employee
select e).ExcludeFields(e=>e.Photo, e=>e.Notes);
' exclude Photo and Notes from employee fetch
Dim q = (From e In metaData.Employee _
Select e).ExcludeFields(Function(e) e.Photo, Function(e) e.Notes)
To fetch these excluded fields later on into the entities returned by the query, you should use the regular mechanisms to do so, as explained in the documentation on Excluding and Including fields (linked above).
Hierarchical sets
Besides
prefetch paths, which offer the ability to fetch a graph of related entities, another mechanism is available to fetch hierarchies of objects: nested queries in the projection. A nested query is a query which produces its own objects and which is present inside a projection of another query. Below is an example of such a query.
var q = from c in metaData.Customer
where c.Country=="USA"
select new
{
Name = c.ContactName,
Orders = from o in metaData.Order
where o.CustomerId == c.CustomerId
select o
};
Dim q = From c In metaData.Customer _
Where c.Country="USA" _
Select New With _
{ _
.Name = c.ContactName, _
.Orders = From o In metaData.Order _
Where o.CustomerId = c.CustomerId _
select o _
}
The query above fetches a set of instances of an anonymous type with two properties:
Name, which contains the ContactName of the customer and
Orders, which contains the OrderEntity instances of the customer.
Linq to LLBLGen Pro fetches nested queries with 1 SQL query per nested query found and merges the parent and child queries together using an efficient mechanism, similar to the one used with prefetch paths. The above Linq query therefore results in two SQL queries being executed: one for the customer data and one for the order data. If the number of parent elements (here: Customers) is below the ParameterisedPrefetchPathThreshold (see Prefetch Paths for more details (
Adapter,
SelfServicing)), for the child elements a parameterized IN clause is used, which is often more efficient than a subquery.
If an entity fetch is used as a nested query (in the query above the Orders are an entity fetch) the code will use a normal entity fetch. Specifying related entities to these entities in nested sets isn't possible using queries like the one above: use prefetch paths on the nested query to fetch related entities to the entities fetched in the nested query.
Hierarchical sets can only occur in projections. This means that you can't fetch related data like in the query above if the projection (the
select clause) isn't the outer select. This means that if you wrap the above query in yet another select, the nested query won't work. This isn't necessary anyhow, so in general you won't run into this.
The nested query has to have a connection with the outer query, otherwise Linq to LLBLGen Pro can't find a proper parent element for a child element or set of child elements. In the query above, a
where clause is used to tie the nested query with the outer query. This can also be done using the specification of a related set, e.g. c.Orders. Specifying the related set will make Linq to LLBLGen Pro use the
correlation relation, which is the relation between parent and child (in the above query: Customer - Order) to build the connection between parent (customer) and child (order). An example of the usage of a correlation relation is the query below, which is a modification of the query above.
var q = from c in metaData.Customer
where c.Country=="USA"
select new
{
Name = c.ContactName,
Orders = c.Orders
};
Dim q = From c In metaData.Customer _
Where c.Country="USA" _
Select New With _
{ _
.Name = c.ContactName, _
.Orders = c.Orders _
}
In the query above, instead of specifying a full query, the specification of the related set (here the set of Orders) is enough to specify what should be fetched as the nested query: all Orders for a fetched customer.
Linq to LLBLGen Pro currently supports only normal parent.field==child.field
where clauses for connecting nested query with outer query.
Calling an in-memory method in the projection
Linq to LLBLGen Pro allows you to call a method in-memory to post-process the results fetched from the database before they're actually returned. This is different from the
Function Mapping feature which converts a .NET method into a DB construct: calling a method in-memory is a method call which isn't converted into SQL. This means that the method call can only be inside the last projection (select) in the query: if it's not, it has to be convertable to SQL as it is part of the SQL query.
Below is an example of this mechanism. It uses a utility class which returns true or false if the customer is matching a rule inside the class.
/// Utility class which obtains value from a webservice
public class InMemoryCallExample
{
public static int GetInterestRate(string customerId)
{
return MyService.GetInterestRate(customerId);
}
}
// this class can now be used in the following query:
var q = from o in metaData.Order
select new
{
o.OrderId,
InterestRate = InMemoryCallExample.GetInterestRate(o.CustomerId)
};
''' Utility class which obtains value from a webservice
Public Class InMemoryCallExample
Public Shared Function GetInterestRate(customerId As String) As Integer
Return MyService.GetInterestRate(customerId)
End Function
End Class
' this class can now be used in the following query:
Dim q = From o In metaData.Order _
Select New With _
{
.OrderId = o.OrderId, _
.InterestRate = InMemoryCallExample.GetInterestRate(o.CustomerId) _
}
It should be noted that this blurs the lines between what's been executed in-memory and what's executed inside the database. This is similar with .NET methods and properties being mapped onto database constructs / functions. Therefore if possible, you should document these mixes of in-memory elements together with query elements in situations where it's not absolutely clear what is executed where, to avoid misinterpretations, where the person reading the code assumes things are executed on the server, while they're actually executed in-memory and vice versa.
In-memory lambda expressions in projections
Besides using .NET method calls in-memory inside a projection, you can also use a Lambda expression. Often a Lambda expression is easier to formulate. In the following example, a Lambda expression is used which performs a SubString() call on the value passed in. The function is used inside the projection of the query by applying it to a string value from the resultset:
// function which applies substring call on input
Func<string, string> stringChopper = s=>s.Substring(0, 3);
// this function can now be used in a query:
var q = from c in metaData.Customer
select stringChopper(c.CompanyName);
' function which applies substring call on input
Dim stringChopper As Func(Of String, String) = Function(s) s.Substring(0, 3)
' this function can now be used in a query:
Dim q = From c in metaData.Customer _
Select stringChopper(c.CompanyName)
The same rules apply to this mechanism as the in-memory method call approach: only use in-memory Func instances (lambda expressions) in the outer projection of the query.